Skip to content

🐛 fix(desktop): 使用真实 CDP 输入并修复搜索框定位 - #268

Open
CavinHuang wants to merge 11 commits into
mainfrom
codex/browser-cdp-actions-262
Open

🐛 fix(desktop): 使用真实 CDP 输入并修复搜索框定位#268
CavinHuang wants to merge 11 commits into
mainfrom
codex/browser-cdp-actions-262

Conversation

@CavinHuang

@CavinHuang CavinHuang commented Aug 20, 2026

Copy link
Copy Markdown
Owner

结果

完成 #262 的真实输入纵向切片,补齐 #253 的百度类搜索框链路,并交付 #263 的首个单写者队列切片。Browser locator 动作不再依赖 element.click()、人工 KeyboardEvent 或直接改 value。

变更

  • click/doubleClick 使用 Input.dispatchMouseEvent 的 move、press、release 完整序列
  • fill 使用真实点击聚焦、全选、Backspace 和 Input.insertText
  • press 通过 DOM 命中目标并使用完整 modifier 生命周期;Enter 支持原生表单提交
  • role 名称支持 aria-labelledby、aria-label、关联 label、title、placeholder、alt 和按钮 value
  • 关联 label 将焦点转交给 label.control
  • 普通页面和 iframe locator 共用真实 CDP 输入路径;iframe 语义按钮/链接通过 DOM.focus + 原生 Enter 激活
  • locator 目标连续两次位置稳定后才执行;fill/type/check/uncheck/select 动作后读取真实状态验证
  • offscreen locator 自动滚动到视口中心
  • 同一 BrowserSession 的变更动作进入单写者队列
  • 队列动作执行前重新校验标签、generation 和 Agent lease;导航后的旧页面动作返回 stale_target
  • 删除原有页面内 click、fill、press、check 合成实现

本轮增量(2026-08-21 审计收口)

集成审计发现用户接管暂停原是 per-tab 门,存在三条绕行路径,已收口:

  • ensure/claim:同 session 存在 paused_by_user Tab 时返回 user_takeover_required——Agent 不能开新 Tab 或认领其他 Tab 绕开暂停继续操作
  • close:目标 Tab 被用户接管时拒绝——Agent 不能关掉用户正在查看的页面
  • cdp(tab_cdp_call):Agent 调用并入同 session 单写者队列,输入类命令登记 inputLedger,其 CDP 输入不再被误判为真实用户输入触发接管
  • finalize/release 等任务清理路径不受拦截,接管态仍可正常结束任务

已知不修(记录边界):scroll:set 双轨(仅 web UI 使用,Agent 工具面不可达);takeover 中断动作不透传 executed 状态(恢复后强制新快照可观察动作是否生效,且 #272 repeat guard 防重复副作用)。

验证

  • Browser action queue、CDP input、locator:11 pass
  • @lume/desktop typecheck 通过
  • Electron Browser Runtime E2E:临时组合 PR 🐛 fix(desktop): 修复 WebMCP 桥接工具调用 #270 后 72 assertions 全部通过
  • 本轮:--popup-only E2E 27 assertions 通过(新增 ensure 绕行拒绝、close 接管 Tab 拒绝两条);queue/ledger/effect 单测 7 pass

后续风险

集成依赖

#266 为独立分支,共同修改 browser-runtime.ts/browser-broker.ts/browser-runtime.e2e.mjs(各 500+ 行冲突)。合并顺序建议:先合本 PR,再 rebase #266;rebase 时需把 desktop 层动作 effect 字段透传到 #266 的 sidecar 工具结果。

Closes #253
Related: #251 #262 #263 #269 #270

将点击、双击、文本输入和按键从页面内合成事件切换为 Chromium CDP Input 序列。普通页面与 iframe locator 统一先解析稳定坐标,再执行真实输入;表单动作读取最终状态,避免静默报告成功。

Constraint: 不依赖尚未合并的内置 Browser Tools PR

Constraint: 保留 select 的标准 DOM 状态设置并验证最终值

Rejected: 继续使用 element.click 与人工 InputEvent | 无法覆盖受控输入和真实用户激活语义

Tested: browser-cdp-input 4 个单元测试通过

Tested: @lume/desktop 类型检查通过
补全可访问名称解析和关联 label 命中,使用可产生文本的原生 CDP Enter 事件,并将 DOM 命中坐标规范为整数。

Constraint: 保持真实 CDP 输入,不回退到 JavaScript 合成事件

Tested: browser-cdp-input 与 browser-locator 8 个单元测试

Tested: @lume/desktop typecheck

Not-tested: 完整 Browser E2E 被现有 WebMCP 基线问题 #269 阻断;新增搜索断言在阻断前全部通过
@CavinHuang CavinHuang changed the title 🐛 fix(desktop): 使用真实 CDP 输入执行浏览器动作 🐛 fix(desktop): 使用真实 CDP 输入并修复搜索框定位 Aug 20, 2026
Electron 会丢弃连续路由到子 frame 的 root-session 指针序列,因此对 iframe 内按钮和链接先用 CDP DOM.focus 聚焦,再发送原生 Enter 完成浏览器语义激活。

Constraint: 不使用 element.click 或合成 MouseEvent

Tested: browser-cdp-input 与 browser-locator 9 个单元测试

Tested: @lume/desktop typecheck

Tested: 合入 #269 修复后 Electron Browser Runtime E2E 70 assertions
@CavinHuang

Copy link
Copy Markdown
Owner Author

追加 iframe 稳定性切片 c6d1fcc:frame button/link 改为 CDP DOM.focus + 原生 Enter 语义激活,规避 Electron root-session 连续指针事件被 Widget 丢弃;未使用 JS click。与 #270/#269 修复组合验证时 Electron Browser Runtime E2E 70 assertions 全通过。

同一 BrowserSession 的变更型请求进入单写者队列。动作真正执行前重新校验标签页身份、generation 与 Agent lease;前一动作导航后,队列中基于旧页面生成的动作返回 stale_target,不再落到新页面。

Constraint: 本提交不实现用户接管暂停与动作后效果等待

Rejected: 仅依赖模型串行发工具 | 并行 tool_use 仍可能同时落到页面

Tested: Browser action queue/CDP/locator 11 tests

Tested: Electron Browser Runtime E2E 72 assertions(临时组合 PR #270 后验证)

Tested: bun run --filter @lume/desktop typecheck
用 CDP 预期输入账本区分 Agent 输入与真实用户输入。用户操作会取消同会话排队动作并锁定为 paused_by_user;只有用户在浏览器面板显式交还后才能恢复,恢复同时推进 generation 使旧定位状态失效。

Constraint: 不新增依赖,保留现有 BrowserSession 串行队列
Constraint: 接管中的标签页作为 handoff 保留,不随 Agent 收尾关闭
Tested: 26 个相关单元测试、desktop/web/sidecar typecheck、Electron popup-only 24 assertions
在真实输入前建立页面状态基线,并进行 300ms 有界等待。动作结果现在区分导航、DOM 变化、新标签页请求、弹窗、下载、崩溃和无可检测变化,不依赖可能被长连接拖死的 networkidle。

Constraint: 等待有界且不新增依赖
Constraint: 用户接管可中断效果等待
Tested: 13 个相关单元测试、desktop typecheck、Electron popup-only 25 assertions
TaTaLiao and others added 3 commits August 21, 2026 10:39
接管暂停原是 per-tab 门(dispatch :836),tab 门之前 return 的
ensure/claim/close 三条路径不受约束:Agent 可开新 Tab 或认领其他
Tab 绕开暂停继续操作,也可关掉用户正在接管的 Tab。现按会话级暂停
语义拦截——同 session 存在 paused_by_user Tab 时拒绝 ensure/claim,
close 目标为接管 Tab 时拒绝。另将 Agent 的 cdp 调用并入同 session
单写者队列,输入类命令登记 inputLedger,避免其 CDP 输入被误判为
真实用户输入触发接管。

Constraint: finalize/release 等任务清理路径不拦,仍可在接管态结束任务
Rejected: 暂停门全局前移到 dispatch 入口 | 会误拦 workspace/vault 等非浏览器路径
Tested: browser-runtime.e2e.mjs --popup-only 27 断言(新增 ensure 绕行/close 接管 Tab 两条)
Tested: browser-action-queue/browser-input-ledger/browser-action-effect 单测 7 pass
Not-tested: agent cdp 输入不误触发接管的 e2e(需开 advancedCdp 全局设置,失败方向保守安全)

Co-Authored-By: Claude <noreply@anthropic.com>
用户展开侧边栏观察 Agent 操作时的滚动(mouseWheel)与指针移动
(mouseMove)被 before-mouse-event 判定为未登记用户输入,直接
pauseAgentControl 终止任务——但这类交互无副作用,不构成接管意图。
现仅按压类(mouseDown/mouseUp)触发接管与 inputSequence 递增;
键盘输入与按压语义不变。实测反馈:Agent 操作期间用户查看被误杀。

Constraint: 拖动滚动条含 mouseDown 仍会触发接管(保守可接受)
Tested: e2e --popup-only 27 断言(接管由真实 mouseDown/Up 触发,语义保持)
Tested: @lume/desktop typecheck

Co-Authored-By: Claude <noreply@anthropic.com>
坐标命中穿透文本输入框的 UA shadow DOM 后,焦点落在 input host,但校验仍以内部展示节点为目标,导致 Enter 路径间歇性返回 stale_target。改为命中宿主控件,与语义引用的命中策略保持一致。

Constraint: 保持真实 CDP 聚焦与键盘输入,不回退到 JavaScript 合成事件

Rejected: 在 RPC catch 白名单 stale_target | 会掩盖错误的焦点目标

Tested: bun test src/browser-cdp-input.test.ts(6 pass)

Tested: bun ./scripts/browser-runtime.e2e.mjs(90 assertions)
CavinHuang pushed a commit that referenced this pull request Aug 21, 2026
真实运行时验证发现工具层与 broker 出口契约错位:broker.dispatch
对所有方法过 adaptBrowserResult(extension 协议形状),list_tabs
返回 {tabs:[{id,title,url}]}(有损压缩,丢 tabId/backend/
profileKind/ownerThreadId)、create_tab 返回 {id}、dialog 返回
{dialog};工具层按 runtime 原生形状消费——ownedAgentTabs 收到
对象抛 browser_internal_error,open 拿不到 tabId,全部浏览器工具
瘫瘓。单测未拦截因 mock 模拟了原生形状,与真实 broker 从未对齐。

- descriptorResult 改无损超集:保留原 descriptor 全部字段并注入
  id 别名;extension 消费者只读 id/title/url,超集兼容
- 工具层解包三处:list_tabs/{tabs}、open/{id}、dialog/{dialog}
- open 的 repeatGuard url 改从工具入参取(归一化后 tab 无 url)
- 单测 mock 全部改为 broker 真实形状,堵住测试绿生产挂的错位
- 另给 25 个浏览器工具 runtimeMetadata 标 requiredDuringSkillScope,
  使其进入 core 注入池——此前全部落入 ToolSearch deferred 池,
  未提升的工具被模型直接调用时报 Unknown tool(与 link 工具同
  模式)

Tested: create-browser-tools/browser-broker 32 pass;@lume/sidecar typecheck 通过
Tested: Electron dev 真实链路:list_tabs/open/snapshot 全通(fill/click 的
actionability_failed 属 #268 分支定位修复域,由集成分支验证)

Co-Authored-By: Claude <noreply@anthropic.com>
TaTaLiao and others added 2 commits August 21, 2026 18:26
did-navigate-in-page(pushState/hash)不替换文档,backendNodeId 与语义
引用在同一文档内保持有效;此前递增 generation 会把快照里仍然有效的 ref
全部误杀为 stale_target(百度等含统计脚本页面的 in-page 风暴期间 fill/click
反复失败,风暴停止后同 ref 又能成功)。真实失效由命中测试兜底。

Constraint: 主 frame did-navigate 仍递增(文档替换语义不变);用户触发
in-page 导航仍撤销 agent lease
Tested: browser-runtime.e2e.mjs 90 断言全绿(stale-navigation-race 语义保持);
browser-sharing/runtime-policy/reference-grants 单测 7 绿;typecheck 绿
Not-tested: 真实百度页面端到端(dev 环境待人工验证)

Co-Authored-By: Claude <noreply@anthropic.com>
fill/type 的 before/observed 值验证用工具层兜底 locator 查询,在同角色
多元素页面(如百度双搜索框)上 strict 多匹配,错误消息不含四个已知码,
被 executeLocatorQuery 兜底映射成 stale_target——语义引用明明已通过
命中验证却被 locator 验证弄死。改为 semanticRef 场景跳过该验证。

Constraint: 纯 locator 路径(无 semanticRef)的值验证语义不变
Rejected: 改 executeLocatorQuery 的兜底映射——会稀释其他 locator 失败的语义
Tested: browser-runtime.e2e.mjs 90 断言全绿;dev 真实链路 RPC 堆栈打点定位
Not-tested: 真实百度页面端到端(修复版 dev 待人工验证)

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

【BUG】【内置浏览器】百度搜索框 locator 填写在可操作性等待后失败

2 participants